#include <GL/glew.h>
#undef HAVE_M_PI
#include <SDL2/SDL.h>
#include <assert.h>
#include <stdint.h>

#include "flac.h"
#include "glUtils.h"
#include "initSDL.h"
#include "svpng.h"

#include "log.h"

#define M_PI 3.141592653589793

#define WIDTH 800
#define HEIGHT WIDTH

typedef struct {
    GLuint program;
    GLuint vPoints;
    GLuint vLPoints;
    GLuint aDeclineTime;
    GLuint vBuff;
} CRT_Screen;

typedef struct {
    GLuint program;
    GLuint a_Vertices;
    GLuint u_FBTexture;
    GLuint u_GuassDir;
    GLuint u_Reslution;
} GUS_Filter;

CRT_Screen crt;
gl_fbo fb0, fb1;
GUS_Filter gus;

#define POINTS (44100 / 60 * 2)

float declineTime[POINTS];
float vVertices[] = {1.0f, -1.0f, 1.0f, 1.0f, -1.0f, -1.0f, -1.0f, 1.0f};

GLuint InitOSC(uint16_t points) {
    GLuint program = InitShader("crt");
    assert(program != 0);

    crt.program = program;
    crt.vPoints = glGetAttribLocation(program, "vPoints");
    crt.aDeclineTime = glGetAttribLocation(program, "aDeclineTime");

    for (int i = 0; i < points; i++) {
        declineTime[i] = i;
    }

    return program;
}

void DrawOSC(FlacData *fd) {
    if ((fd->total_samples * fd->channels - fd->cnt / sizeof(int16_t)) <
        POINTS) {
        return;
    }
    glBindFramebuffer(GL_FRAMEBUFFER, fb0.fbo);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glViewport(0, 0, fb0.w, fb0.h);

    glUseProgram(crt.program);

    glVertexAttribPointer(crt.aDeclineTime, 1, GL_FLOAT, GL_FALSE, 0,
                          declineTime);
    glEnableVertexAttribArray(crt.aDeclineTime);

    glVertexAttribPointer(crt.vPoints, 2, GL_SHORT, GL_FALSE, 0,
                          &fd->wav[fd->cnt / sizeof(int16_t)]);
    glEnableVertexAttribArray(crt.vPoints);

    glDrawArrays(GL_LINE_STRIP, 0, POINTS);
}

GLuint InitFBGuassFilter() {
    GLuint program = InitShader("gus");
    assert(program != 0);
    gus.program = program;

    gus.a_Vertices = glGetAttribLocation(program, "a_Position");
    gus.u_FBTexture = glGetUniformLocation(program, "u_FBTexture");
    gus.u_GuassDir = glGetUniformLocation(program, "u_GuassDir");
    gus.u_Reslution = glGetUniformLocation(program, "u_Reslution");

    return program;
}

void DrawFBGuass(GLuint texture, gl_fbo *fb, int dir) {
    glBindFramebuffer(GL_FRAMEBUFFER, fb->fbo);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glViewport(0, 0, fb->w, fb->h);
    glUseProgram(gus.program);

    glUniform1i(gus.u_GuassDir, dir);
    glUniform2f(gus.u_Reslution, fb->w, fb->h);
    glVertexAttribPointer(gus.a_Vertices, 2, GL_FLOAT, GL_FALSE, 0, vVertices);
    glEnableVertexAttribArray(gus.a_Vertices);
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, texture);
    glUniform1i(gus.u_FBTexture, 0);
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
    glBindTexture(GL_TEXTURE_2D, 0);
}

void audio_callback(void *userdata, uint8_t *stream, int len) {
    FlacData *fd = (FlacData *)userdata;
    memset(stream, 0, len);
    int audio_len =
        (fd->total_samples * fd->channels * sizeof(int16_t)) - fd->cnt;
    int len_ = len < audio_len ? len : audio_len;
    SDL_MixAudio(stream, &((uint8_t *)fd->wav)[fd->cnt], len_,
                 SDL_MIX_MAXVOLUME);
    fd->cnt += len_;
}

int main(int argc, char *argv[]) {
    int run = 1, pause = 0;
    FlacData fd;

    LOG_INIT();

    SDL_Window *win = InitSDLWindow("oscillofun", WIDTH, HEIGHT);
    SDL_Surface *sur = SDL_CreateRGBSurfaceWithFormat(0, WIDTH, HEIGHT, 32,
                                                      SDL_PIXELFORMAT_RGBA32);

    fd_decode(&fd, "oscillofun.flac");

    InitSDLAudio(fd.sample_rate, AUDIO_S16, fd.channels,
                 fd.sample_rate / 120 * sizeof(int16_t) * fd.channels,
                 audio_callback, &fd);

    InitFrameBuffer(&fb0, WIDTH, HEIGHT);
    InitFrameBuffer(&fb1, WIDTH, HEIGHT);

    InitOSC(POINTS);
    InitFBGuassFilter();

    {
        glEnable(GL_LINE_SMOOTH);
        glEnable(GL_POLYGON_SMOOTH);
        glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);
        glHint(GL_POLYGON_SMOOTH_HINT, GL_NICEST);
        glEnable(GL_BLEND);
        glBlendFunc(GL_SRC_ALPHA, GL_DST_ALPHA);
        glClearColor(0.0, 0.0, 0.0, 1.0);
        glLineWidth(2);
    }

    SDL_PauseAudio(pause);
    gl_fbo fb_dis = {.w = WIDTH, .h = HEIGHT, .fbo = 0, .tex = 0};
    while (run) {
        SDL_Event event;
        while (SDL_PollEvent(&event)) {
            if (event.type == SDL_QUIT) {
                run = 0;
            } else if (event.type == SDL_MOUSEMOTION) {

            } else if (event.type == SDL_KEYDOWN) {
                switch (event.key.keysym.sym) {
                case SDLK_c:
                    memset(sur->pixels, 0x00, WIDTH * HEIGHT * 4);
                    glReadPixels(0, 0, WIDTH, HEIGHT, GL_RGBA, GL_UNSIGNED_BYTE,
                                 sur->pixels);
                    FILE *fp = fopen("capture.png", "wb");
                    svpng(fp, sur->w, sur->h, sur->pixels, 1);
                    fclose(fp);
                    LOG_INFO("save to \"capture.png\" file\n");
                    break;
                case SDLK_ESCAPE:
                    run = 0;
                    break;
                case SDLK_SPACE:
                    pause = !pause;
                    SDL_PauseAudio(pause);
                    break;
                }
            }
        }

        if (!pause) {
            DrawOSC(&fd);
            DrawFBGuass(fb0.tex, &fb1, 0);
            DrawFBGuass(fb1.tex, &fb_dis, 1);
            SDL_GL_SwapWindow(win);
        }
    }
    SDL_AudioQuit();
    SDL_FreeSurface(sur);
    SDL_DestroyWindow(win);
    fd_free(&fd);

    LOG_EXIT();
    return 0;
}
